﻿using JetBrains.Annotations;
using LightCAD.Core;
using LightCAD.Three;
using OpenTK.Graphics.OpenGL;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using loop = LightCAD.Three.JsArr<LightCAD.Three.Vector2>;
using loop3 = LightCAD.Three.JsArr<LightCAD.Three.Vector3>;
namespace QdArch.ThreeUtils
{
    public class GeometryData
    {
        public JsArr<double> Position { get; set; }
        public JsArr<Int32> Index { get; set; }
        public JsArr<double> Uvs { get; set; }
        public JsArr<int> FaceUvs { get; set; }
        public Vector2 Repeat { get; set; }
        public JsArr<Vector3> TopPoints { get; set; }
        public JsArr<Vector3> BottomPoints { get; set; }
        public JsArr<Int32> HoleIndices { get; set; }
        public GeometryData Edge { get; set; }

        public JsArr<Int32> TopFaceIndex { get; set; }

        public JsArr<Int32> BottomFaceIndex { get; set; }
        public JsArr<Int32> InnerFaceIndex { get; set; }
        public JsArr<Int32> OuterFaceIndex { get; set; }

        public JsArr<Int32> curveFaceIndex { get; set; }
        public GeometryData Add(GeometryData geoData) 
        {
            var offset = this.Position.length / 3;
            this.Position.AddRange(geoData.Position);
            this.Index.AddRange(geoData.Index.Select(i=>i+ offset));
            this.Position.AddRange(geoData.Position);
            this.TopPoints.AddRange(geoData.TopPoints);
            this.TopFaceIndex.AddRange(geoData.TopFaceIndex.Select(i => i + offset));
            this.BottomPoints.AddRange(geoData.BottomPoints);
            this.BottomFaceIndex.AddRange(geoData.BottomFaceIndex.Select(i => i + offset));
            this.HoleIndices.AddRange(geoData.HoleIndices.Select(h => h + offset));
            this.InnerFaceIndex.AddRange(geoData.InnerFaceIndex.Select(i => i + offset));
            this.OuterFaceIndex.AddRange(geoData.OuterFaceIndex.Select(i => i + offset));
            this.curveFaceIndex.AddRange(geoData.curveFaceIndex.Select(i => i + offset));
            return this;
        }
        public BufferGeometry GetBufferGeometry()
        {
            var geo = new BufferGeometry();
            geo.attributes.position = new BufferAttribute(Position.ToArray(), 3);
            geo.setIndex(this.Index.ToArray());
            return geo;
        }
    }
    public sealed class CoordShape3 
    {
        public Shape shape;
        public JsArr<loop> Points2d;
        public JsArr<JsArr<loop>> Holes2d;
        public Matrix4 coordMatrix;
        public double z;
        public JsArr<JsArr<loop3>> Holes;
        public JsArr<loop3> Points;
        public CoordShape3(Shape shape, Matrix4 coordMatrix, double z)
        {
            this.shape = shape;
            this.coordMatrix = coordMatrix;
            this.z = z;
            var subPoints = shape.getPoints();
            this.Points2d =new JsArr<loop>() ;
            this.Points = new JsArr<loop3>();
            this.Holes2d = new JsArr<JsArr<loop>>();
            this.Holes = new JsArr<JsArr<loop3>>();
            if (this.shape.holes?.Count > 0)
            {
                var clipper = new ClipperWrapper();
                clipper.Set(new List<List<Vector2>> { subPoints.ToList() });
                var holes = shape.holes.Select(p => p.getPoints().ToList()).ToList();
                clipper.Clips.AddRange(holes);
                clipper.Execute(ClipType.ctDifference);
                var clipperShapes = clipper.GetShapes(1e-5);
                for (int i = 0; i < clipperShapes.Count; i++)
                {
                    var cs = clipperShapes[i];
                    this.Points2d.push(cs.Points.ToJsArr());
                    this.Points.push(cs.Points.Select(p => new Vector3(p, z).applyMatrix4(coordMatrix)).ToJsArr());
                    this.Holes2d.push(cs.Holes.Select(h => h.Points.ToJsArr()).ToJsArr());
                    this.Holes.push(cs.Holes.Select(h => h.Points.Select(p => new Vector3(p, z).applyMatrix4(coordMatrix)).ToJsArr()).ToJsArr());
                }
            }
            else
            {
                this.Points2d.push(subPoints);
                this.Points.push(subPoints.Select(p => new Vector3(p, z).applyMatrix4(coordMatrix)).ToJsArr());
                this.Holes2d.push(new JsArr<loop>());
                this.Holes.push(new JsArr<loop3>());
            }
        }
    }
    public static class GeoUtil
    {
        public static GeometryData GetStretchGeometryData(Shape shape, Matrix4 coordMat, double front, double back) 
        {
            var frontShape = new CoordShape3(shape, coordMat, front);
            var backShape = new CoordShape3(shape, coordMat, back);
            return GetMappedShapeGeometryData(frontShape, backShape);
        }
        public static GeometryData GetMappedShapeGeometryData(CoordShape3 topShape, CoordShape3 bottomShape, bool hasTopFace = true, bool hasBottomFace = true)
        {
            GeometryData result = null;
            for (int pi = 0; pi < topShape.Points.length; pi++)
            {
                var topPoints2d = topShape.Points2d[pi];
                var topPoints3d = topShape.Points[pi];
                var topHoles2d= topShape.Holes2d[pi];
                var topHoles3d= topShape.Holes[pi];

                var botPoints2d = bottomShape.Points2d[pi];
                var botPoints3d = bottomShape.Points[pi];
                var botHoles2d = bottomShape.Holes2d[pi];
                var botHoles3d = bottomShape.Holes[pi];

                TriangulateOutput output = new TriangulateOutput();
                JsArr<JsArr<int>> faces = new JsArr<JsArr<int>>();
                JsArr<int> holeIndices = new JsArr<int>();
                if (hasTopFace || hasBottomFace)
                {
                    var shape = topShape.shape;
                    faces = triangulateShape(topPoints2d, topHoles2d, output);
                }

                var topPoints = new JsArr<Vector3>().concat(topPoints3d);
                for (var i = 0; i < topHoles3d.length; i++)
                {
                    topPoints = topPoints.concat(topHoles3d[i]);
                }

                var bottomPoints = new JsArr<Vector3>().concat(botPoints3d);
                for (var i = 0; i < botHoles3d.length; i++)
                {
                    bottomPoints = bottomPoints.concat(botHoles3d[i]);
                }
                holeIndices = output.holeIndices;
                var data = CreateMappedGeometryData(topPoints, bottomPoints, faces, holeIndices, hasTopFace, hasBottomFace);
                data.TopPoints = topPoints;
                data.BottomPoints = bottomPoints;
                data.HoleIndices = holeIndices;
                if (result == null)
                    result = data;
                else
                    result.Add(data);
            }
           
            return result;
        }
        //根据两组对应的3D形状数据获得几何体数据
        public static GeometryData CreateMappedGeometryData(JsArr<Vector3> topPoints, JsArr<Vector3> bottomPoints, JsArr<JsArr<int>> faces, JsArr<int> holeIndices, bool hasTopFace, bool hasBottomFace)
        {
            var vcount = topPoints.length;
            var idxOfs = 0;
            var posArr = new JsArr<double>();
            var indexArr = new JsArr<int>();
            //var uvsArr = new JsArr<double>();
            var topFaceIndex = new JsArr<int>();
            var bottomFaceIndex = new JsArr<int>();
            var curveFaceIndex = new JsArr<int>();
            var facePoints = new Dictionary<int, JsArr<Vector3>>();
            int face = -1;
            if (hasTopFace)
            {
                for (var i = 0; i < topPoints.length; i++)
                {
                    posArr.push(topPoints[i].x, topPoints[i].y, topPoints[i].z);
                }

                for (var i = 0; i < faces.length; i++)
                {
                    var f = faces[i];
                    indexArr.push(f[0] + idxOfs, f[1] + idxOfs, f[2] + idxOfs);
                    topFaceIndex.push(f[0] + idxOfs, f[1] + idxOfs, f[2] + idxOfs);
                }
                idxOfs += vcount;

                face++;
                facePoints.Add(face, topPoints);

                //var res = generateUvs(topPoints, face);
                //var uvs = res.Item1;
                //uvsArr.AddRange(uvs);
                //var rep = res.Item2;
            }
            if (hasBottomFace)
            {
                for (var i = 0; i < bottomPoints.length; i++)
                {
                    posArr.push(bottomPoints[i].x, bottomPoints[i].y, bottomPoints[i].z);
                }
                for (var i = 0; i < faces.length; i++)
                {
                    var f = faces[i];
                    indexArr.push(f[0] + idxOfs, f[2] + idxOfs, f[1] + idxOfs);//这里要换方向
                    bottomFaceIndex.push(f[0] + idxOfs, f[2] + idxOfs, f[1] + idxOfs);
                }
                idxOfs += vcount;

                //face++;
                //var res = generateUvs(bottomPoints, face);
                //var uvs = res.Item1;
                //uvsArr.AddRange(uvs);
                //var rep = res.Item2;

                face++;
                facePoints.Add(face, bottomPoints);

            }
            Vector3 pbtmNext;
            Vector3 ptopNext;
            var len = vcount;
            if (holeIndices.length > 0)
                len = holeIndices[0];//如果有洞，外边到第一个洞的起始

            var idxCount = 0;
            for (var i = 0; i < len; i++)
            {
                var vecs = new JsArr<Vector3>();
                var pbtm = bottomPoints[i];
                var ptop = topPoints[i];
                if (i == len - 1)
                {
                    pbtmNext = bottomPoints[0];
                    ptopNext = topPoints[0];
                }
                else
                {
                    pbtmNext = bottomPoints[i + 1];
                    ptopNext = topPoints[i + 1];
                }
                posArr.push(pbtm.x, pbtm.y, pbtm.z);
                posArr.push(pbtmNext.x, pbtmNext.y, pbtmNext.z);
                posArr.push(ptopNext.x, ptopNext.y, ptopNext.z);
                posArr.push(ptop.x, ptop.y, ptop.z);

                //face++;
                vecs.push(pbtm, pbtmNext, ptopNext, ptop);
                //var res = generateUvs(vecs, face);
                //var uvs = res.Item1;
                //uvsArr.AddRange(uvs);
                //var rep = res.Item2;

                face++;
                facePoints.Add(face, vecs);

                indexArr.push(i * 4 + idxOfs, i * 4 + 1 + idxOfs, i * 4 + 2 + idxOfs);
                indexArr.push(i * 4 + idxOfs, i * 4 + 2 + idxOfs, i * 4 + 3 + idxOfs);
                curveFaceIndex.push(i * 4 + idxOfs, i * 4 + 1 + idxOfs, i * 4 + 2 + idxOfs);
                curveFaceIndex.push(i * 4 + idxOfs, i * 4 + 2 + idxOfs, i * 4 + 3 + idxOfs);
                idxCount += 4;
            }

            if (holeIndices.length > 0)
            {
                idxOfs += idxCount;
                for (var i = 0; i < holeIndices.length; i++)
                {
                    var end = vcount;
                    if (i + 1 < holeIndices.length)
                    {
                        end = holeIndices[i + 1];
                    }
                    var start = holeIndices[i];
                    idxCount = 0;
                    for (var j = start; j < end; j++)
                    {
                        var vecs = new JsArr<Vector3>();
                        var pbtm = bottomPoints[j];
                        var ptop = topPoints[j];
                        if (j == end - 1)
                        {
                            pbtmNext = bottomPoints[start];
                            ptopNext = topPoints[start];
                        }
                        else
                        {
                            pbtmNext = bottomPoints[j + 1];
                            ptopNext = topPoints[j + 1];
                        }
                        posArr.push(pbtm.x, pbtm.y, pbtm.z);
                        posArr.push(pbtmNext.x, pbtmNext.y, pbtmNext.z);
                        posArr.push(ptopNext.x, ptopNext.y, ptopNext.z);
                        posArr.push(ptop.x, ptop.y, ptop.z);

                        //face++;
                        vecs.push(pbtm, pbtmNext, ptopNext, ptop);
                        //var res = generateUvs(vecs, face);
                        //var uvs = res.Item1;
                        //uvsArr.AddRange(uvs);
                        //var rep = res.Item2;
                        face++;
                        facePoints.Add(face, vecs);

                        indexArr.push(idxCount + idxOfs, idxCount + 1 + idxOfs, idxCount + 2 + idxOfs);//这里方向已经朝外了
                        indexArr.push(idxCount + idxOfs, idxCount + 2 + idxOfs, idxCount + 3 + idxOfs);
                        curveFaceIndex.push(idxCount + idxOfs, idxCount + 1 + idxOfs, idxCount + 2 + idxOfs);//这里方向已经朝外了
                        curveFaceIndex.push(idxCount + idxOfs, idxCount + 2 + idxOfs, idxCount + 3 + idxOfs);
                        idxCount += 4;
                    }
                    idxOfs += idxCount;
                }
            }

            //var res = generateUvs(facePoints);
            //var uvsArr = res.Item1;
            //var faceUvs = res.Item2;

            var data = new GeometryData();
            data.Position = posArr;
            data.Index = indexArr;
            //data.Uvs = uvsArr;
            //data.FaceUvs = faceUvs;
            data.TopFaceIndex = topFaceIndex;
            data.BottomFaceIndex = bottomFaceIndex;
            data.InnerFaceIndex = curveFaceIndex.Take(curveFaceIndex.Count / 2 - 9).ToJsArr();
            data.curveFaceIndex = curveFaceIndex;
            try
            {
                data.OuterFaceIndex = curveFaceIndex.clone().splice(curveFaceIndex.Count / 2 - 3, data.InnerFaceIndex.Count);
            }
            catch (Exception)
            {

            }
            return data;
        }
        public static JsArr<JsArr<int>> triangulateShape(JsArr<Vector2> contour, JsArr<JsArr<Vector2>> holes, TriangulateOutput output)
        {
            var faceIndexs = ShapeUtils.triangulateShape(contour, holes, output);
            var vertices = new JsArr<Vector2>();
            vertices.push(contour.ToArray());
            holes.ForEach(h => vertices.push(h.ToArray()));
            for (int i = 0; i < faceIndexs.length; i++)
            {
                var faceIndex = faceIndexs[i];
                if (ShapeUtils.isClockWise(getIndexPoints(vertices, faceIndex)))
                {
                    var temp = faceIndex[0];
                    faceIndex[0] = faceIndex[2];
                    faceIndex[2] = temp;
                }
            }
            return faceIndexs;
        }

        public static BufferGeometry GeoApplyMatrix3d(BufferGeometry geo, Matrix3d m3d)
        {
            var posArr = geo.attributes.position.array;
            for (int i = 0; i < posArr.Length; i+=3)
            {
                Vector2d v2 = new Vector2d(posArr[i], posArr[i + 1]);
                v2=m3d.MultiplyPoint(v2);
                posArr[i] = v2.X;
                posArr[i+1] = v2.Y;
            }
            return geo;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static JsArr<Vector2> getIndexPoints(IEnumerable<Vector2> contour, IList<int> indexArr)
        {
            return indexArr.Select(i => contour.ElementAt(i)).ToJsArr();
        }
    }
    public static class ShapeUtil 
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Shape Rect(double minx, double miny, double maxx, double maxy)
        {
            return new Shape(new JsArr<Vector2> {
                new Vector2(minx,miny),
                new Vector2(maxx,miny),
                new Vector2(maxx,maxy),
                new Vector2(minx,maxy),
            });
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Shape Circle(double cx, double cy, double radius)
        {
            var shape = new Shape();
            shape.arc(cx, cy, radius, 0, Math.PI * 2, false);
            return shape;
        }
        public static Shape Hexagon(double cx, double cy, double radius) 
        {
            var circle = new ArcCurve(cx, cy, radius, 0, Math.PI * 2, false);
            return new Shape(circle.getPoints(6));
        }
    }
    public static class CurveUtil 
    {
        /// <summary>
        /// 线线相交
        /// </summary>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <returns></returns>
        public static Vector2 LineIntersectLine2D(Vector2 p1, Vector2 dir1, Vector2 p2, Vector2 dir2, double eps = MathUtil.EPS)
        {
            if (MathUtil.NumEQ(dir1.clone().cross(dir2), 0, eps))
                return null;//共线
            if (MathUtil.NumEQ(dir1.y, 0, eps) && MathUtil.NumEQ(dir2.x, 0, eps))
            {//两线正交垂直
                return new Vector2(p2.x, p1.y);
            }
            else if (MathUtil.NumEQ(dir1.x, 0, eps) && MathUtil.NumEQ(dir2.y, 0, eps))
            {//两线正交垂直
                return new Vector2(p1.x, p2.y);
            }
            double a1 = dir1.y, b1 = -dir1.x, c1 = p1.y * dir1.x - p1.x * dir1.y;
            double a2 = dir2.y, b2 = -dir2.x, c2 = p2.y * dir2.x - p2.x * dir2.y;
            var d = a1 * b2 - a2 * b1;
            var x = (b1 * c2 - b2 * c1) / d;
            var y = (c1 * a2 - c2 * a1) / d;
            return new Vector2(x, y);
        }
        /// <summary>
        /// 线段与x垂直线相交
        /// </summary>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <returns></returns>
        public static Vector2[] LineIntersectX2D(Vector2 p1, Vector2 p2, double x, out string ext_Type, bool isExt = true)
        {
            ext_Type = "";
            List<Vector2> points = new List<Vector2>();
            if (MathUtil.NumEQ(p1.x, x) && MathUtil.NumEQ(p2.x, x))
            {//线与x重合
                if (isExt)
                    ext_Type = "coin";
                return new Vector2[] { p1, p2 };
            }
            if (MathUtil.NumEQ(p1.x, x))
            {//p1与x重合
                if (isExt)
                    ext_Type = "end1";
                return new Vector2[] { p1 };
            }
            if (MathUtil.NumEQ(p2.x, x))
            {//p2与x重合
                if (isExt)
                    ext_Type = "end2";
                return new Vector2[] { p2 };
            }

            var minx = p1.x < p2.x ? p1.x : p2.x;
            var maxx = p1.x > p2.x ? p1.x : p2.x;
            if (x < minx || x > maxx)
            {
                if (isExt)
                    ext_Type = "none";
                return new Vector2[] { };
            }
            if (isExt)
                ext_Type = "cross";
            if (MathUtil.NumEQ(p1.y, p2.y))
            {//线垂直X
                return new Vector2[] { new Vector2(x, p1.y) };
            }

            var y = (p2.y - p1.y) / (p2.x - p1.x) * (x - p1.x) + p1.y;
            return new Vector2[] { new Vector2(x, y) };
        }

        /// <summary>
        /// 线段与x垂直线相交
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="x"></param>
        /// <returns></returns>
        public static Vector2[] LineIntersectX2D(Vector2 p1, Vector2 p2, double x)
        {
            string ext_Type;
            return LineIntersectX2D(p1, p2, x, out ext_Type, false);
        }


        /// <summary>
        /// 两根宽线相交
        ///线线相交
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="dir1"></param>
        /// <param name="width1"></param>
        /// <param name="p2"></param>
        /// <param name="dir2"></param>
        /// <param name="widith2"></param>
        /// <returns></returns>
        public static Vector2[] LineWIntersectLineW2D(Vector2 p1, Vector2 dir1, double width1, Vector2 p2, Vector2 dir2, double widith2)
        {
            if (dir1.clone().cross(dir2) == 0) return null;//共线
            double hw1 = width1 / 2, hw2 = widith2 / 2;
            var zero = new Vector2();
            var vdir1 = dir1.clone().rotateAround(zero, (double)(Math.PI / 2)).normalize();//逆时针转90度
            var vdir2 = dir2.clone().rotateAround(zero, (double)(Math.PI / 2)).normalize();//逆时针转90度
            var ofs_p1_1 = p1.clone().add(vdir1.clone().multiplyScalar(hw1));
            var ofs_p1_2 = p1.clone().add(vdir1.clone().multiplyScalar(-hw1));
            var ofs_p2_1 = p2.clone().add(vdir2.clone().multiplyScalar(hw2));
            var ofs_p2_2 = p2.clone().add(vdir2.clone().multiplyScalar(-hw2));

            var inter_1_1 = LineIntersectLine2D(ofs_p1_1, dir1, ofs_p2_1, dir2);
            var inter_1_2 = LineIntersectLine2D(ofs_p1_1, dir1, ofs_p2_2, dir2);
            var inter_2_1 = LineIntersectLine2D(ofs_p1_2, dir1, ofs_p2_1, dir2);
            var inter_2_2 = LineIntersectLine2D(ofs_p1_2, dir1, ofs_p2_2, dir2);
            return new Vector2[] { inter_1_1, inter_1_2, inter_2_1, inter_2_2 };
        }

    }
    public static class MathUtil
    {
        public const double DoublePi = Math.PI * 2;
        public const double HalfPi = Math.PI / 2;
        public const double Pi = Math.PI;
        public const double EPS = 0.01;
        public static int FloatSortEQ(double a, double b)
        {
            if (a > b)
                return 1;
            else if (a < b)
                return -1;
            else
                return 0;
        }
        /// <summary>
        /// 大于
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool FloatGT(double a, double b, double? eps = null)
        {
            if (eps == null)
                eps = EPS;
            return (Math.Abs(a - b) >= eps) && a >= b;
        }
        /// <summary>
        /// 小于
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool FloatLT(double a, double b, double? eps = null)
        {
            if (eps == null)
                eps = EPS;
            return (Math.Abs(a - b) > eps) && a < b;
        }
        /// <summary>
        /// 大于等于
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool FloatGE(double a, double b, double? eps = null)
        {
            if (eps == null)
                eps = EPS;
            return a >= b || (Math.Abs(a - b) < eps);
        }


        /// <summary>
        /// 小于等于
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool FloatLE(double a, double b, double? eps = null)
        {
            if (eps == null)
                eps = EPS;
            return a <= b || (Math.Abs(a - b) < eps);
        }
        /// <summary>
        /// 等于
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool FloatEQ(double a, double b, double? eps = null)
        {
            if (eps == null)
                eps = EPS;
            return (Math.Abs(a - b) < eps);
        }


        public static bool NumEQ(double a, double b, double? eps = null)
        {
            if (eps == null)
                eps = EPS;
            return (Math.Abs(a - b) < eps);
        }
        public static bool NumEQ(double a, double b, double eps)
        {
            return (Math.Abs(a - b) < eps);
        }
    }
}
